package controller;

import entities.*;
import listener.MessageListener;
import listener.PiecesListener;
import org.json.JSONObject;
import util.Mp3Player;
import util.MyClient;
import view.ChessBoard;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/***
 * Author: Mr.Zhao, Time: 2018/4/30/19:03
 * Description: 游戏控制器，可被继承
 */
public class GameController extends MouseAdapter implements PiecesListener {
    /* 棋盘上的所有棋子 */
    protected List<BasePiece> pieces = new CopyOnWriteArrayList<>();
    /* 由坐标和棋子组成的键值对 */
    protected Map<Point, BasePiece> pointPieceMap = new HashMap<Point, BasePiece>();

    private ChessBoard chessBoard;

    /* 之前被点击的棋子 */
    private BasePiece clickedPiece;

    /* 该哪方走棋，默认红方先行 */
    private Color whosTurn = Color.RED;

    /* 是否是单人模式 */
    // private boolean isSingleMode = true;
    /* 双人模式下的我方棋子颜色 */
    private Color myColor;
    /* 记录棋子走动前位置的坐标点 */
    private RecordPoint recordPoint = null;
    /* 棋子周围的环 */
    private PieceLoop pieceLoop = null;

    public GameController(ChessBoard chessBoard) {
        this.chessBoard = chessBoard;
    }

    /***
     * @author 赵永远
     * @param e
     */
    @Override
    public void mouseClicked(MouseEvent e) {
        Component component = e.getComponent();
        /* 点到的是棋子 */
        if (component instanceof BasePiece) {
            BasePiece piece = (BasePiece) component;
            /* 不该自己走 */
            if (myColor != whosTurn) {
                return;
            }
            //System.out.println(piece.getPieceName() + "被点击");
            /* 点的是可走的棋子 */
            if (piece.getColor() == whosTurn) {
                new Thread(new SoundDriver("select.mp3")).start();

                /* 先清除原来坐标，再绘制新坐标 */
                if (clickedPiece != null) {
                    removeAvailablePointsFromChessBoard(clickedPiece);
                }
                addAvailabelPointsToChessBoard(piece);
            }
            /* 点到的不该走的棋子 */
            else {
                /* 之前没选中该走的棋子，直接不起作用 */
                if (clickedPiece == null) {
                    new Thread(new SoundDriver("goerror.mp3"));
                    return;
                }
                /* 之前选中了该走的棋子 */
                Set<AvailablePoint> availablePoints = clickedPiece.getAvailablePoints();
                for (AvailablePoint availablePoint : availablePoints) {
                    /* 如果该棋子在攻击范围内，则吃掉该棋子 */
                    if (availablePoint.getPosition().equals(piece.getPosition())) {
                        removeAvailablePointsFromChessBoard(clickedPiece);
                        eatPiece(clickedPiece, piece);
                        return;
                    }
                }
                /* 点到不该点的棋子不应该将clickedPiece为其赋值，因此要返回 */
                return;
            }
            clickedPiece = piece;
        }
        /* 点到的是面板 */
        if (component instanceof ChessBoard) {
            int px = e.getX();
            int py = e.getY();
            //不在棋盘坐标范围内，或者之前没有棋子被选中或者不该选中的棋子走，直接不做任何事
            // TODO：修正坐标点
            if (px < chessBoard.getMargin() || py < chessBoard.getMargin() ||
                    clickedPiece == null ||
                    clickedPiece.getColor() != whosTurn) {
                return;
            }
            /* 将像素坐标转化为棋盘坐标 */
            int x = px / chessBoard.getCellWidth();
            int y = py / chessBoard.getCellHeight();
            Set<AvailablePoint> availablePoints = clickedPiece.getAvailablePoints();
            for (AvailablePoint availablePoint : availablePoints) {
                if (availablePoint.getPosition().equals(new Point(x, y))) {
                    removeAvailablePointsFromChessBoard(clickedPiece);
                    clickedPiece.moveTo(x, y);
                    return;
                }
            }
            /*能够执行到此步说明坐标不在可走范围内*/
            new Thread(new SoundDriver("goerror.mp3")).start();
        }
    }

    /**
     * 把被点击的棋子可走坐标添加到棋盘上,使其可见
     *
     * @param piece 要显示的可走坐标的棋子
     * @author 赵永远
     */
    private void addAvailabelPointsToChessBoard(BasePiece piece) {
        //System.out.println("添加" + piece.getPieceName() + "的可移动坐标");
        Set<AvailablePoint> availablePoints = piece.getAvailablePoints();
        for (AvailablePoint availablePoint : availablePoints) {
            chessBoard.add(availablePoint);
        }
        chessBoard.repaint();
    }

    /**
     * 从面板中移除坐标点，使其不再可见
     *
     * @author 赵永远
     */
    private void removeAvailablePointsFromChessBoard(BasePiece piece) {
        System.out.println("移除" + piece.getPieceName() + "的可移动坐标");
        Set<AvailablePoint> availablePoints = piece.getAvailablePoints();
        for (AvailablePoint point : availablePoints) {
            chessBoard.remove(point);
        }
        chessBoard.repaint();
    }

    /**
     * 1.将棋子添加到面板上
     * 2.将棋子保存到HashSet的pieces中
     * 3.将棋子保存到键值对中
     * 4.为棋子注册监听器
     *
     * @param piece
     * @author 赵永远
     */
    public void addPiece(BasePiece piece) {
        piece.addMouseListener(this);
        piece.addPiecesListener(this);
        chessBoard.add(piece);
        this.pieces.add(piece);
        this.pointPieceMap.put(piece.getPosition(), piece);
        /* 每添加一个棋子进来就更新所有棋子的可用坐标 */
        for (BasePiece piece1 : pieces) {
            piece1.updateAvailablePoints(pointPieceMap);
        }
    }

    /**
     * 1.移除面板上的棋子
     * 2.将棋子从HashSet的pieces中移除
     * 3.把棋子从键值对中移除
     * 4.移除棋子的监听器
     *
     * @param piece
     * @author 赵永远
     */
    public void removePiece(BasePiece piece) {
        if (this.pieces.contains(piece)) {
            chessBoard.remove(piece);
            this.pieces.remove(piece);
            this.pointPieceMap.remove(piece.getPosition());
            piece.removeMouseListener(this);
            piece.removePiecesListener(this);
            for (BasePiece piece1 : pieces) {
                piece1.updateAvailablePoints(pointPieceMap);
            }
        }
    }

    /**
     * 吃掉棋子
     * 判断双方的老将是否还在以判断游戏是否结束
     *
     * @param killer 要吃的棋子
     * @param killed 要被吃掉的棋子
     * @author 赵永远
     */
    public void eatPiece(BasePiece killer, BasePiece killed) {
        new Thread(new SoundDriver("eat.mp3")).start();

        if (killer.getColor() == killed.getColor()) {
            return;
        }

        removePiece(killed);
        killer.moveTo(killed.getPosition().x, killed.getPosition().y);

        if (killed.getPieceName().equals("帅") || killed.getPieceName().equals("将")) {
            if (killed.getColor() == myColor) {
                JOptionPane.showConfirmDialog(chessBoard, "您已阵亡！", "败寇", JOptionPane.DEFAULT_OPTION);
            } else {
                JOptionPane.showConfirmDialog(chessBoard, "恭喜您获胜！", "成王", JOptionPane.DEFAULT_OPTION);
            }
            MyClient client = MyClient.getInstance();
            JSONObject json = new JSONObject();
            json.put("flag", "游戏结束");
            client.sendMessage(json.toString());
            this.endGame();
        }

    }

    /**
     * 实现棋子的移动事件:
     * 1.每次移动都会重新设置到父布局（即棋盘面板）的位置
     * 2.移除由该棋子产生的在面板中的坐标点
     * 3.重新计算下次可走坐标点
     * 4.设置走棋轮到对方
     * 5.播放移动音频
     * 6.更新键值对
     * 7.记录该棋子的移动，分别在原位置和当前位置绘制标记
     *
     * @param piece
     * @author 赵永远
     */
    public void afterPieceMoved(BasePiece piece) {
        new Thread(new SoundDriver("go.mp3")).start();
        int x = piece.getPosition().x;
        int y = piece.getPosition().y;
        int diameter = piece.getDiameter();
        piece.setBounds(chessBoard.getMargin() + x * chessBoard.getCellWidth() - diameter / 2,
                chessBoard.getMargin() + y * chessBoard.getCellHeight() - diameter / 2,
                diameter, diameter);

        String tempColorName;
        if (piece.getColor().equals(BasePiece.RED_PIECE)) {
            tempColorName = "红方 ";
        } else {
            tempColorName = "黑方 ";
        }
        System.out.println(tempColorName + piece.getPieceName() + "从(" + piece.getLastPosition().x + "," +
                piece.getLastPosition().y + ")移动到(" + x + "," + y + ")");

        recordPoint = new RecordPoint(piece.getColor(), piece.getLastPosition());
        chessBoard.add(recordPoint);
        pieceLoop = new PieceLoop(piece);
        chessBoard.add(pieceLoop);
        chessBoard.repaint();

        clickedPiece = null;

        this.pointPieceMap.remove(piece.getLastPosition());
        this.pointPieceMap.put(piece.getPosition(), piece);

        if (piece.getColor() == BasePiece.RED_PIECE) {
            whosTurn = BasePiece.BLACK_PIECE;
        } else {
            whosTurn = BasePiece.RED_PIECE;
        }

        for (BasePiece piece1 : pieces) {
            piece1.updateAvailablePoints(pointPieceMap);
        }

        isCheckmate(piece);
    }

    /**
     * 将军
     */
    protected boolean isCheckmate(BasePiece piece) {
        Jiang jiang = null;
        boolean isCheckmate = false;
        boolean isDead = false;
        BasePiece whichOneWillBeKiller = null;

        for (BasePiece piece1 : pieces) {
            if (piece1 instanceof Jiang && piece1.getColor() != piece.getColor()) {
                jiang = (Jiang) piece1;
            }
        }
        if (jiang == null||(jiang.getColor()==piece.getColor())) {
            return false;
        }

        Set<AvailablePoint> allApoints = new HashSet<AvailablePoint>();
        for (BasePiece piece1 : pieces) {
            if (piece1.getColor() != piece.getColor()) {
                continue;
            }

            Set<AvailablePoint> temApoints = new HashSet<AvailablePoint>();
            temApoints = piece1.getAvailablePoints();
            if (temApoints.contains(new AvailablePoint(jiang.getPosition()))) {
                isCheckmate = true;
                whichOneWillBeKiller = piece1;
            }
            allApoints.addAll(temApoints);
        }


        /***
         * TODO:是否被将死
         * 1.将无路可走
         * 2.没有棋子可以挡住将军棋子的路
         * 3.没有棋子可以杀掉将军的棋子
         */
        if (allApoints.containsAll(jiang.getAvailablePoints())) {
            Set<AvailablePoint> allApoints2 = new HashSet<AvailablePoint>();
            for (BasePiece piece1 : pieces) {
                if (piece1.getColor() == piece.getColor()) {
                    continue;
                }
                allApoints2.addAll(piece1.getAvailablePoints());
            }
        }

        if (isCheckmate && !isDead) {
            System.out.println("将军！");
            new Thread(new SoundDriver("Man_jiangjun.mp3")).start();
            return piece.getColor()==myColor;
        }
        return false;
    }

    /**
     * 棋子移动前需触发的事件
     * 1.判断是否会被敌方将军，如果会，则不能走
     * 2.将上次棋子移动记录的坐标(RecordPoint)删除
     * 3.将上次棋子移动记录的环(pieceLoop)删除
     *
     * @param piece
     */
    public void beforePieceMoved(BasePiece piece) {

        if (recordPoint != null) {
            chessBoard.remove(recordPoint);
        }
        if (pieceLoop != null) {
            chessBoard.remove(pieceLoop);
        }
    }

    /**
     * 内部类，一个声音驱动线程
     */
    public static class SoundDriver implements Runnable {
        private String fileName;

        public SoundDriver(String fileName) {
            this.fileName = fileName;
        }

        public void run() {
            Mp3Player mp3Player = new Mp3Player();
            mp3Player.play(fileName);
        }
    }

    /**
     * 初始化棋盘
     */
    public void initChessBord(Color myColor) {
        this.myColor = myColor;
        Color opColor = BasePiece.BLACK_PIECE;
        if (myColor == BasePiece.BLACK_PIECE) {
            opColor = BasePiece.RED_PIECE;
        }
        for (int x = 0; x <= 9; x += 8) {
            /* 初始化我方车 */
            Ju jum = new Ju(x, 9, myColor);
            this.addPiece(jum);
            /* 初始化对方车 */
            Ju juo = new Ju(x, 0, opColor);
            this.addPiece(juo);
        }

        for (int x = 1; x <= 9; x += 6) {
            /* 初始化我方馬*/
            Ma mam = new Ma(x, 9, myColor);
            this.addPiece(mam);
            /* 初始化对方馬*/
            Ma mao = new Ma(x, 0, opColor);
            this.addPiece(mao);
        }

        for (int x = 1; x <= 9; x += 6) {
            /* 初始化我方炮 */
            Pao paom = new Pao(x, 7, myColor);
            this.addPiece(paom);
            /* 初始化对方炮*/
            Pao pao = new Pao(x, 2, opColor);
            this.addPiece(pao);
        }

        for (int x = 2; x <= 9; x += 4) {
            /* 初始化我方象/相 */
            Xiang xiangm = new Xiang(x, 9, myColor);
            this.addPiece(xiangm);
            /* 初始化对方象/相*/
            Xiang xiango = new Xiang(x, 0, opColor);
            this.addPiece(xiango);
        }

        for (int x = 0; x <= 9; x += 2) {
            /* 初始化我方卒/兵 */
            Zu zum = new Zu(x, 6, myColor);
            this.addPiece(zum);
            /* 初始化对方卒/兵*/
            Zu zuo = new Zu(x, 3, opColor);
            this.addPiece(zuo);
        }

        Shi shi1 = new Shi(3, 9, myColor);
        this.addPiece(shi1);
        Shi shi2 = new Shi(5, 9, myColor);
        this.addPiece(shi2);
        Shi shi3 = new Shi(3, 0, opColor);
        this.addPiece(shi3);
        Shi shi4 = new Shi(5, 0, opColor);
        this.addPiece(shi4);

        Jiang jiang1 = new Jiang(4, 9, myColor);
        this.addPiece(jiang1);
        Jiang jiang2 = new Jiang(4, 0, opColor);
        this.addPiece(jiang2);
        chessBoard.repaint();
    }

    /**
     * 清除棋盘上的棋子
     */
    public void resetChessBord() {
        for (BasePiece piece : pieces) {
            this.removePiece(piece);
            piece.removePiecesListener(this);
        }
        chessBoard.removeAll();
        chessBoard.repaint();
    }

    protected void startGame(Color myColor) {
        initChessBord(myColor);
        chessBoard.addMouseListener(this);
        new Thread(new BgmDriver()).start();
    }

    protected void endGame() {
        resetChessBord();
        chessBoard.removeMouseListener(this);
        //重置先手为红棋
        this.whosTurn=Color.RED;
    }


    /**
     * 一个内部类，驱动游戏不停播放音频
     *
     * @author 赵永远
     */
    private static class BgmDriver implements Runnable {

        public void run() {
            Mp3Player mp3Player = new Mp3Player();
            mp3Player.play("bg.mp3");
            while (mp3Player.getPlayer().isComplete()) {
                mp3Player.play("bg.mp3");
            }
        }

    }

}
